package com.ilia.rubik;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.ilia.util.set.BitPlaneSet;
import com.ilia.util.timer.ITimer;


public class TraverseIndicator {
	
	
	static class IndicatorInfo {
		int id;
		String name;
		int[] indicator;
		int depth;
		Set values;
		int hash_depth;
	}
	List<IndicatorInfo> indicators;
	
	int[] target_cube;
	//Map<String, int[]> indicators;
	//int max_moves = 0;
	char[] current_moves = null;
	int[] cube;
	ITimer timer;
	PossibleMoves pm;
	//Map<String,Set> found_indicator_values;
	
	boolean cube_can_be_moved_to_target(int[] current_cube, int moves_left) {
		if (indicators==null) return true;
		for (IndicatorInfo indicator : indicators) {
			if (indicator.depth<moves_left) continue;
			int intIndicator = Cube.cube_to_indicator_hash(current_cube, indicator.indicator);
			if (!indicator.values.contains(intIndicator)) {
				return false;
			}
		}
		return true;
	}
	
	void traverse() {
		init();
		if (!load()) {
			work();
			UV();
			while (isDown()) {
				if (isRight()) {
					right();
					work();
					UV();
				} else {
					down();
					work();
				}
			}
			save();
		}
	//	finish();
	}
	
	
	void save() {
		
		try {
			
			for (IndicatorInfo indicator : indicators) {
				String fileName = "ind_"+indicator.name+"_"+indicator.hash_depth+"_"+indicator.depth+".bin";
					FileOutputStream fos = new FileOutputStream(fileName);
					ObjectOutputStream oos = new ObjectOutputStream(fos);
					oos.writeObject(indicator.values);
					oos.flush();
					oos.close();
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	boolean load() {
		try {
			
			for (IndicatorInfo indicator : indicators) {
				int intIndicator = Cube.cube_to_indicator_hash(cube, indicator.indicator);
				String fileName = "ind_"+indicator.name+"_"+indicator.hash_depth+"_"+indicator.depth+".bin";
				
					FileInputStream fis = new FileInputStream(fileName);
					ObjectInputStream ois = new ObjectInputStream(fis);
					Object obj = ois.readObject();
					Set setObj = (Set)obj;
					indicator.values = setObj;
					ois.close();
			}
			return true;
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return false;
	}
	
	public void configure(int max_moves, PossibleMoves pm, int[] target_cube, Map<String,int[]> inds, ITimer timer) {
		this.indicators = new ArrayList<IndicatorInfo>();
		
		int hash_depth = 30;
		
		for (String name : inds.keySet()) {
			IndicatorInfo info = new IndicatorInfo();
			info.name = name;
			info.indicator = inds.get(name);
			info.depth = max_moves;
			info.hash_depth = hash_depth;
			info.values = new BitPlaneSet(hash_depth);
			indicators.add(info);
		}
		this.pm = pm;
		this.target_cube = target_cube;
		this.timer = timer;
	}
	
	boolean isDown() {
		return (current_moves.length > 0);
	}
	
	boolean isUp() {
		boolean result = false;
		for (IndicatorInfo indicator : indicators) {
			if (current_moves.length < indicator.depth) {
				result = true;
				break;
			}
		}
		return result;
	}
	
	boolean isRight() {
		try {
			timer.startTimer("IND_IS_RIGHT");
			char lastMove = current_moves[current_moves.length - 1];
			char[] possibleNextMoves = pm.cachedPossibleRightMoves(current_moves,current_moves.length - 1);
			if (lastMove==possibleNextMoves[possibleNextMoves.length-1]) return false;
			return true;
		} finally {
			timer.stopTimer("IND_IS_RIGHT");
		}
	}
	
	void up() {
		timer.startTimer("IND_UP");
		char[] new_moves = Cube.copyChars(current_moves, 0, current_moves.length + 1);
		char nextMove = pm.cachedPossibleRightMoves(current_moves, current_moves.length)[0];
		new_moves[new_moves.length - 1] = nextMove;
		current_moves = new_moves;
		timer.startTimer("IND_ROTATE");
		Cube.move(nextMove, cube,cube);
		timer.stopTimer("IND_ROTATE");
		timer.stopTimer("IND_UP");
	}
	
	void work() {
		timer.startTimer("IND_WORK");
		for (IndicatorInfo indicator : indicators) {
			int intIndicator = Cube.cube_to_indicator_hash(cube, indicator.indicator);
			indicator.values.add(Integer.valueOf(intIndicator));
		}
		timer.stopTimer("IND_WORK");
	}

	
	void init() {
		current_moves = new char[0];
		cube = Cube.copy(target_cube);
	}
	
	
	void finish() {
		timer.startTimer("IND_FINISH");
		StringBuffer sb = new StringBuffer();
		for (IndicatorInfo indicator : indicators) {
			sb.append("Indicator "+indicator.name+": "+indicator.values.size()+" values\n");
		}
		System.out.println(sb.toString());
		timer.stopTimer("IND_FINISH");
	}

	
	void UV() {
		while (isUp()) {
			up();
			work();
		}

	}
	
	
	void right() {
		timer.startTimer("IND_RIGHT");
		char lastMove = current_moves[current_moves.length - 1];
		
		timer.startTimer("IND_ROTATE");
		Cube.undo_move(lastMove, cube,cube);
		timer.stopTimer("IND_ROTATE");

		char nextMove =' ';
		char[] possibleNextMoves = pm.cachedPossibleRightMoves(current_moves, current_moves.length-1);
		for (int i=0;i<possibleNextMoves.length-1;i++) {
			if (possibleNextMoves[i]==lastMove) {
				nextMove = possibleNextMoves[i+1];
				break;
			}
		}

		current_moves[current_moves.length - 1] = nextMove;

		timer.startTimer("IND_ROTATE");
//		cube =Cube.move(nextMove, cube);
		Cube.move(nextMove, cube, cube);
		timer.stopTimer("IND_ROTATE");
		timer.stopTimer("IND_RIGHT");
		

	}

	void down() {
		timer.startTimer("IND_DOWN");
		char lastMove = current_moves[current_moves.length - 1];

		timer.startTimer("IND_ROTATE");
		Cube.undo_move(lastMove, cube,cube);
		timer.stopTimer("IND_ROTATE");

		current_moves = Cube.copyChars(current_moves, 0, current_moves.length - 1);
		timer.stopTimer("IND_DOWN");
	}
}
